#!/usr/bin/env python
# encoding: utf-8


"""
角相关的知识点
1 角的象限
2 与角终边相同的角的集合
3 与角终边相同的最小正角
4 与角终边相同的最大负角
@file: jiao.py
@time: 2016/10/19 下午5:16
"""
from functools import partial
from mathsolver.functions.root.jiefangchenzu import *
from mathsolver.functions.budengshi import common_opers as co
from sympy import pi, ceiling, floor, fraction
from sympy.core.numbers import Pi
from operator import itemgetter

First_Quad = Interval.open(0, pi / 2)
Second_Quad = Interval.open(pi / 2, pi)
Third_Quad = Interval.open(pi, 3 * pi / 2)
Fourth_Quad = Interval.open(3 * pi / 2, 2 * pi)


def quad_interval(quad_value):
    """
    求象限角的取值范围
    :param quad_value: 象限
    :return: 
    >>> quad_interval(1)
    (2*pi*k, 2*pi*k + pi/2)
    """
    quad_value = sympify(quad_value)
    k_symbol = sympify('k')
    _2k_pi = 2 * k_symbol * pi
    if quad_value == 1:
        return Interval.open(_2k_pi, _2k_pi + pi / 2)
    elif quad_value == 2:
        return Interval.open(_2k_pi + pi / 2, _2k_pi + pi)
    elif quad_value == 3:
        return Interval.open(_2k_pi + pi, _2k_pi + 3 * pi / 2)
    elif quad_value == 4:
        return Interval.open(_2k_pi + 3 * pi / 2, _2k_pi + 2 * pi)
    raise Exception('illegality quadrant value')


# 角度相关的知识点
def sam_ts_angles(angle, intl=None):  # 与角A终边相同的角的集合
    """
    返回与角A终边相同的角的集合
    :param angle: 角的度数。用pi形式表示或者用度数
    :param intl: 角度区间
    :return:
    """

    def angles(a):
        a = sympify(a)
        if isinstance(a, Pi) or pi in a.args:  # 弧度制
            return a + sympify('2*k*pi')
        else:  # 角度制
            return a + sympify('360*k')

    if intl:  # 如果有区间限制的话
        ll = intl.left
        r = intl.right
        tmp_as = angles(angle)
        k_symbol = list(tmp_as.free_symbols)[0]
        k0 = ceiling(list(solveset(tmp_as - ll, domain=S.Reals))[0])
        k1 = floor(list(solveset(tmp_as - r, domain=S.Reals))[0])
        return FiniteSet(*map(partial(tmp_as.subs, k_symbol), range(k0, k1 + 1)))

    else:
        return angles(angle)


def min_pos_angle(a):
    """
    求与角a 终边相同的正角
    :param a:
    :return:
    >>> min_pos_angle('pi/3')
    pi/3
    >>> min_pos_angle(20)
    20
    """
    a = sympify(a)
    if a.is_real:
        sa = sam_ts_angles(a)
    else:
        sa = a
    k_symbol = list(sa.free_symbols)[0]
    k = list(solveset(sa, domain=S.Reals))[0]
    k0 = ceiling(k)  # 将k向上取整得到k0
    return sa.subs(k_symbol, k0)


# print min_pos_angle('2*k*pi - 17/3*pi')


def max_neg_angle(a):
    """
    与角a 终边相同的最大负角
    :return:
    >>> max_neg_angle('pi/3')
    -5*pi/3
    >>> max_neg_angle(20)
    -340
    """
    sa = sam_ts_angles(a)
    k_symbol = list(sa.free_symbols)[0]
    k = list(solveset(sa, domain=S.Reals))[0]
    k0 = floor(k)  # 将k向下取整得到k0
    return sa.subs(k_symbol, k0)


def trig_f_quad_pm(trig_type):
    """
    三角函数各个象限的正负
    """
    trig_type = str(trig_type).strip()
    if trig_type == 'sin':
        return [1, 1, -1, -1]
    elif trig_type == 'cos':
        return [1, -1, -1, 1]
    elif trig_type == 'tan':
        return [1, -1, 1, -1]
    elif trig_type == 'cot':
        return [1, -1, 1, -1]


def get_trig_quads(trig_type, trig_value):
    """
    求某个三角函数值所在的象限
    :param trig_type:
    :param trig_value:
    :return:
    >>> get_trig_quads('cos', sympify('-4/5'))
    [2, 3]
    """
    quads = trig_f_quad_pm(trig_type)
    quad_indes = list(enumerate(quads, start=1))
    quad_indes = filter(lambda qi: qi[1] * trig_value > 0, quad_indes)
    return map(itemgetter(0), quad_indes)


def angle_interval(a, x_i):
    """
    角度的取值范围
    :param a: 角度表达式
    :param x_i: 原始角度的区间
    :return:
    """
    symons, const = co.symoms_and_const(a)
    sym_coefs = map(lambda s: list(s.as_coefficients_dict().items())[0], symons)
    i = Interval(-1, 1)
    for m, c in sym_coefs:
        if c > 0:
            i = Interval(i.left * c, i.right * c)
        else:
            i = Interval(i.right * c, i.left * c)
    i = Interval(i.left + 1, i.right - 1)
    return i


def angle_interval_list(param_angle_interval, target_angle_interval):
    """
    给定角的区间[含参数的],具体的角的区间列表，求出角的具体区间的列表
    :param param_angle_interval: 
    :param target_angle_interval: 
    :return: 
    >>> angle_interval_list(Interval(sympify('k*pi'), sympify('k*pi+pi/2')), Interval(0, 2 * pi))
    [[0, pi/2], [pi, 3*pi/2]]
    """
    param_intl_left = param_angle_interval.left
    param_intl_right = param_angle_interval.right
    target_intl_left = target_angle_interval.left
    target_intl_right = target_angle_interval.right
    param_symbol = list(param_intl_left.free_symbols)[0]
    interval_list = []
    start = ceiling(list(co.isolve_eq(BaseEq([param_intl_left - target_intl_left, 0])).values()[0])[0])
    max_intervals = 10
    for i in range(max_intervals):
        param_value = start + i
        left_value = param_intl_left.subs(param_symbol, param_value)
        right_value = param_intl_right.subs(param_symbol, param_value)
        left_ineq = '<' if target_angle_interval.left_open else '<='
        right_ineq = '<' if target_angle_interval.right_open else '<='
        if solveset(str(target_intl_left) + left_ineq + str(left_value), domain=S.Reals) and solveset(
                                str(right_value) + right_ineq + str(target_intl_right)):
            interval_list.append(Interval(left_value, right_value))
        else:
            break
    return interval_list


def angle_interval_quad(a_i):
    """
    根据角区间求角所在的象限
    :param a_i:
    :return:
    >>> angle_interval_quad(Interval(sympify('2*k*pi + pi/2'), sympify('2*k*pi + pi*3/2')))
    [2, 3]
    >>> angle_interval_quad(Interval(sympify('k*pi'), sympify('k*pi+pi/2')))
    [1, 3]
    >>> angle_interval_quad(Interval(sympify('0'), sympify('pi/2')))
    [1]
    """

    def _cycle_angle_intervals(a_interval):
        """
        求一个角区间的一个周期的具体区间列表
        :param a_interval: 
        :return: 
        """
        intl_left = a_interval.left
        intl_right = a_interval.right
        param_symbol = list(intl_left.free_symbols)[0]
        start = ceiling(list(list(co.isolve_eq(BaseEq([intl_left, 0])).values())[0])[0])
        end = floor(list(list(co.isolve_eq(BaseEq([intl_right - 2 * pi, 0])).values())[0])[0])
        return [Interval(intl_left.subs(param_symbol, i), intl_right.subs(param_symbol, i)) for i in
                range(start, end + 1)]

    if a_i.left.is_real:
        angle_intervals = [a_i]
    else:
        angle_intervals = _cycle_angle_intervals(a_i)
    quads = []
    for a_intl in angle_intervals:
        if First_Quad.intersect(a_intl):
            quads.append(1)
        if Second_Quad.intersect(a_intl):
            quads.append(2)
        if Third_Quad.intersect(a_intl):
            quads.append(3)
        if Fourth_Quad.intersect(a_intl):
            quads.append(4)
    return list(set(quads))


def angle_interval_quad_v2(a_i):
    """
    根据角区间求角所在的象限
    :param a_i:
    :return:
    """
    left_value = a_i.left
    k_symb = list(left_value.free_symbols)[0]
    coef = left_value.coeff(k_symb) / pi
    if fraction(coef)[1] == 1:  # 如果为整数 则判断是偶数 还是奇数
        pass
    else:  # 如果是分数
        pass

    l_a = min_pos_angle(a_i.left)
    r_a = min_pos_angle(a_i.right)
    a_i = Interval(l_a, r_a)
    quads = []
    if First_Quad.intersect(a_i):
        quads.append(1)
    if Second_Quad.intersect(a_i):
        quads.append(2)
    if Third_Quad.intersect(a_i):
        quads.append(3)
    if Fourth_Quad.intersect(a_i):
        quads.append(4)
    return quads
